import java.util.ArrayList;
import java.util.TreeSet;
import java.util.TreeMap;



//This class is designed to hold messages and their associated Prophet statistics
//The class provides basic add/remove functionality for messages and Prophet statistics
//and the ability to return values for both in a String format that is designed to be useful
//for transmission to other nodes.
public class MessageStatistics {
	final Double gamma = .98;
	final Double Pinit = .75;
	final Double beta = .25;
	public TreeMap<String,Double> prophetProbabilities;
	private TreeMap<String,MessageNode> messages;

	public MessageStatistics(){
		prophetProbabilities = new TreeMap<String,Double>();
		messages = new TreeMap<String,MessageNode>();

	}

	public ArrayList<String> deliverMessages(String destination){
		synchronized (messages){
			ArrayList<String> deliveredMessages = new ArrayList<String>();
			ArrayList<String> allMessages = new ArrayList<String>(messages.keySet());
			for (int i = 0; i < allMessages.size(); i++){
				//System.out.println(messages.get(allMessages.get(i)).getMessageDestination() + "," + destination);
				if (messages.get(allMessages.get(i)).getMessageDestination().equalsIgnoreCase(destination)){
					deliveredMessages.add(messages.get(allMessages.get(i)).toString());
					RemoveMessage(allMessages.get(i));
				}
			}
			return deliveredMessages;
		}
	}

	public int messageCount(){
		synchronized (messages){
			return messages.size();
		}
	}

	public ArrayList<String> removeDeadMessages(){
		synchronized (messages){
			ArrayList<String> deadMessages = new ArrayList<String>();
			ArrayList<String> allMessages = new ArrayList<String>(messages.keySet());
			for (int i = 0; i < allMessages.size(); i++){
				if (messages.get(allMessages.get(i)).getMessageTTL() <= 0){
					deadMessages.add(messages.get(allMessages.get(i)).toString());
					RemoveMessage(allMessages.get(i));
				}
			}
			return deadMessages;
		}
	}

	public Double updateProphet(String key){
		synchronized (prophetProbabilities){
			Double PabOld = getProphet(key);
			Double newvalue = PabOld + (1-PabOld) * Pinit;
			return setProphet(key, newvalue);
		}
	}

	public Double transitiveProphetUpdate(String c, String b, Double Pbc ){
		synchronized (prophetProbabilities){
			Double Pab = getProphet(b);
			Double PacOld = getProphet(c);
			Double PacNew = PacOld + (1 - PacOld) * Pab * Pbc * beta;
			return setProphet(c, PacNew);
		}
	}

	public void ageAllProphet(){
		synchronized (prophetProbabilities){
			String[] keys = prophetProbabilities.keySet().toArray(new String[0]);
			for (int i = 0; i<keys.length; i++){
				ageProphet(keys[i]);
			}
		}
	}

	public Double ageProphet(String key){
		synchronized (prophetProbabilities){
			Double PabOld = getProphet(key);
			Double agedvalue = PabOld * gamma;
			return setProphet(key, agedvalue);
		}
	}

	public Double setProphet(String key, Double value){
		synchronized (prophetProbabilities){

			if (value < 0.0) {
				return prophetProbabilities.put(key.toUpperCase(), 0.0);
			} else {
				return prophetProbabilities.put(key.toUpperCase(), value);
			}
		}

	}

	public Double getProphet(String key){
		synchronized (prophetProbabilities){
			if (prophetProbabilities.containsKey(key.toUpperCase())){
				return prophetProbabilities.get(key.toUpperCase());
			} else {
				return 0.0;
			}
		}
	}

	public ArrayList<String> getProphetKeys(){
		synchronized (prophetProbabilities){
			ArrayList<String> keys = new ArrayList<String>(prophetProbabilities.keySet());
			return keys;
		}
	}

	public String AllProphets(){
		synchronized (prophetProbabilities){
			String[] keys = prophetProbabilities.keySet().toArray(new String[0]);
			Double[] values = prophetProbabilities.values().toArray(new Double[0]);
			String pairs = "";
			if (keys.length == values.length){
				for (int i = 0; i<keys.length; i++){
					pairs = pairs + keys[i] + " " + values[i] +"\n";
				}
			}

			return pairs;
		}
	}

	public Double averageProphet(){
		synchronized (prophetProbabilities){
			ArrayList<Double> values = new ArrayList<Double>(prophetProbabilities.values());
			ArrayList<String> pKeys = new ArrayList<String>(prophetProbabilities.keySet());
			ArrayList<MessageNode> mKeys = new ArrayList<MessageNode>(messages.values());
			int unknownHosts = 0;
			ArrayList<String> countedDestinations = new ArrayList<String>();

			for (int i = 0; i < mKeys.size(); i++){

				if ((!pKeys.contains(mKeys.get(i).getMessageDestination())) && (!countedDestinations.contains(mKeys.get(i).getMessageDestination()))){
					unknownHosts++;
					countedDestinations.add(mKeys.get(i).getMessageDestination());
				}
			}
			if (values.size() == 0){
				return 0.0;
			}
			Double average = 0.0;
			for (int i = 0; i < values.size(); i++){
				average = average + values.get(i);
			}
			average = average/(double)(values.size() + unknownHosts);
			return average;
		}
	}

	public void DecrementAllTTL(){
		synchronized (messages){
			ArrayList<MessageNode> allMessages = new ArrayList<MessageNode>(messages.values());
			for (int i = 0; i < allMessages.size(); i++){
				allMessages.get(i).decrementTTL();
			}
		}
	}

	public boolean AddMessage(String originator, String destination, int ID, int TTL){
		synchronized (messages){
			MessageNode temp = new MessageNode(originator, destination, ID, TTL);
			return (messages.put(temp.getDistinctID(), temp) == null);
		}
	}

	public boolean RemoveMessage(String ID){
		synchronized (messages){
			return (messages.remove(ID) == null);
		}
	}

	public String AllMessages(){
		synchronized (messages){
			ArrayList<MessageNode> allMessages = new ArrayList<MessageNode>(messages.values());
			String allMessageString = "";
			for (int i = 0; i < allMessages.size(); i++){
				allMessageString = allMessageString + allMessages.get(i).toString() + "\n";
			}

			return allMessageString;
		}
	}

	public String AllMessageIDs(){
		synchronized (messages){
			ArrayList<MessageNode> allMessages = new ArrayList<MessageNode>(messages.values());
			String messageIDs = "";
			for (int i = 0; i < allMessages.size(); i++){
				//System.out.println("ID:" + allMessages.get(i).getDistinctID());
				messageIDs = messageIDs + allMessages.get(i).getDistinctID() + "\n";
			}
			return messageIDs;
		}
	}

	public String ActiveMessageIDs(){
		synchronized (messages){
			ArrayList<MessageNode> allMessages = new ArrayList<MessageNode>(messages.values());
			String messageIDs = "";
			for (int i = 0; i < allMessages.size(); i++){
				if (allMessages.get(i).getMessageTTL()>0){
					messageIDs = messageIDs + allMessages.get(i).getDistinctID() + "\n";
				}

			}
			return messageIDs;
		}
	}


	public String getMessage(String messageID){
		synchronized (messages){
			if (messages.get(messageID) == null){
				return null;
			}
			return messages.get(messageID).toString() + "\n";
		}
	}

	public int getMessageTTL(String messageID){
		synchronized (messages){
			if (messages.get(messageID) == null){
				return 0;
			}
			return messages.get(messageID).getMessageTTL();
		}
	}

	public String getMessageDestination(String messageID){
		synchronized (messages){
			if (messages.get(messageID) == null){
				return null;
			}
			return messages.get(messageID).getMessageDestination();
		}
	}

	//A class to hold a message in a way that can be used in a TreeSet
	//along with some standard "getters and setters," a compareTo function,
	//and a toString function. Also includes a function that decrements the TTL
	//for the message.
	private class MessageNode implements Comparable<MessageNode>{
		private String messageOriginator;
		private String messageDestination;
		private int messageID;
		private int messageTTL;

		public MessageNode(String o, String d, int i, int t){
			messageOriginator = o;
			messageDestination = d;
			messageID = i;
			messageTTL = t;
		}

		public void decrementTTL(){
			messageTTL--;
			if (messageTTL < 0){
				messageTTL = 0; 
			}
		}

		@Override
		public int compareTo(MessageNode m) {
			String myID = this.getDistinctID();
			String targetID = m.getDistinctID();
			return myID.compareToIgnoreCase(targetID);
		}

		public String getMessageOriginator() {
			return messageOriginator;
		}

		public void setMessageOriginator(String messageOriginator) {
			this.messageOriginator = messageOriginator;
		}

		public String getMessageDestination() {
			return messageDestination;
		}

		public void setMessageDestination(String messageDestination) {
			this.messageDestination = messageDestination;
		}

		public int getMessageID() {
			return messageID;
		}

		public void setMessageID(int messageID) {
			this.messageID = messageID;
		}

		public int getMessageTTL() {
			return messageTTL;
		}

		public void setMessageTTL(int messageTTL) {
			this.messageTTL = messageTTL;
		}

		public String getDistinctID(){
			return messageOriginator + " " + messageID + " " + messageDestination;
		}



		public String toString(){
			return messageOriginator + ";" + messageDestination + ";" + messageID + ";" + messageTTL;
		}

	}




}